﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using InfoExpress.WinApp.Utils;
using System.Configuration;
using InfoExpress.HIV.Domains;
using InfoExpress.HIV.WinApp.Utils;

namespace InfoExpress.HIV.WinApp.Business
{
  public class Permission
  {
    #region Members and Properties

    private string _role = string.Empty;
    private Opc _roleOpc = Opc.None;
    private SUsers _user = null;

    public string Username { get; set; }
    public string UnitName { get; set; }
    public string UnitCode { get; set; }

    /// <summary>
    /// Super Unit Code: 01
    /// </summary>
    public const string SuperUnitCode = "01";

    #endregion

    #region Constructors

    private Permission()
    {
    }

    #endregion

    #region Role Constants

    /// <summary>
    /// u_admin
    /// </summary>
    public const string Admin = "u_admin";
    /// <summary>
    /// u_input
    /// </summary>
    public const string Writer = "u_input";
    /// <summary>
    /// u_read
    /// </summary>
    public const string Reader = "u_read";
    /// <summary>
    /// u_doctor
    /// </summary>
    public const string Doctor = "u_doctor";
    /// <summary>
    /// u_yta
    /// </summary>
    public const string Nurse = "u_yta";
    /// <summary>
    /// u_tuvan
    /// </summary>
    public const string Adviser = "u_tuvan";

    #endregion

    #region Permission of OPC

    /// <summary>
    /// Permission of OPC.
    /// </summary>
    public enum Opc
    {
      /// <summary>
      /// Do not have permission.
      /// </summary>
      None = 0,
      /// <summary>
      /// Permission 1.
      /// </summary>
      Permission01 = 1,
      /// <summary>
      /// Permission 2.
      /// </summary>
      Permission02 = 2,
      /// <summary>
      /// Permission 3.
      /// </summary>
      Permission03 = 3
    }

    #endregion

    #region Function Names

    /// <summary>
    /// Function of Applications
    /// </summary>
    public enum Functions
    {
      DoiMatKhau,
      DangKyDieuTriIO,
      DangKyDieuTriArv,
      TimKiem,
      DuyetDangKy,
      ChamDutDieuTri,
      ThayOpc,
      ThayPhacDo,
      XemBaoCaoThang,
      CapNhatThongTinDieuTri,
      XemDanhSach,
      XemDanhSachArv,
      XemDanhSachCapThe,
      XemDanhSachDieuTri,
      XemDanhSachPhacDo,
      XemDanhDachChuaDuyet,
      XemDanhDachDaDuyet,
      XemLichSuChuyenBenh,
      DangKyCapThe,
      ThayDoiThongTinCapThe
    }

    #endregion

    #region Function-Roles

    private readonly string[] DoiMatKhau = { Admin, Reader, Writer };
    private readonly string[] DangKyDieuTriIO = { Writer };
    private readonly string[] DangKyDieuTriArv = { Writer };
    private readonly string[] TimKiem = { Admin, Reader, Writer };
    private readonly string[] DuyetDangKy = { Admin };
    private readonly string[] ChamDutDieuTri = { Writer };
    private readonly string[] ThayOpc = { Writer };
    private readonly string[] ThayPhacDo = { Writer };
    private readonly string[] XemBaoCaoThang = { Admin, Writer };
    private readonly string[] CapNhatThongTinDieuTri = { Admin, Writer };
    private readonly string[] XemDanhSach = { Writer };
    private readonly string[] XemDanhSachArv = { Admin, Reader, Writer };
    private readonly string[] XemDanhSachCapThe = { Admin, Reader, Writer };
    private readonly string[] XemDanhSachDieuTri = { Admin, Reader, Writer };
    private readonly string[] XemDanhSachPhacDo = { Writer };
    private readonly string[] XemDanhDachChuaDuyet = { Admin, Reader, Writer };
    private readonly string[] XemDanhDachDaDuyet = { Admin, Reader, Writer };
    private readonly string[] XemLichSuChuyenBenh = { Admin, Writer };
    private readonly string[] DangKyCapThe = { Admin };
    private readonly string[] ThayDoiThongTinCapThe = { Admin, Writer };

    #endregion

    #region Public Static Methods

    public static Permission GetPermission(string userName, string password)
    {
      Permission permission;

      try
      {
        //Get user by userName
        int total = 0;
        TList<SUsers> userList =
          GlobalCache.UsersService.GetPaged(
            String.Format("UserName='{0}'", userName), string.Empty, 0, 1, out total);

        

        if (total > 0)
        {
          TList<UtDmdv> donViList =
              GlobalCache.DonViService.GetPaged(
                String.Format("f_madv='{0}'", userList[0].Office), string.Empty, 0, 1, out total);

          if (total > 0)
          {
            if (VerifyPassword(userName, password, userList[0]))
            {
              permission = new Permission();
              permission.Username = userName;
              permission.UnitCode = userList[0].Office;
              permission.UnitName = donViList[0].FTendv;
              permission._user = userList[0];

              //Get DonVi & role: _roleOpc
              permission._roleOpc = GetOpcPermission(permission.UnitCode,
                donViList[0].FDt1 ?? false, 
                donViList[0].FDt2 ?? false);
              //Get Role: _role
              //TODO: Get s_UserGroup
              permission._role = string.Format("{0};{1};{2};", Admin, Reader, Writer);
            }
            else
              permission = null;
          }
          else
            permission = null;
        }
        else
          permission = null;
      }
      catch (Exception ex)
      {
        Logger.LogCritical("Permission::GetPermission", ex, "General", null);
        throw ex;
      }
      
      #region Test
      //try
      //{
      //  if (ConfigurationManager.AppSettings["IsTest"] == "1")
      //  {
      //    permission = new Permission();
      //    permission.UnitName = ConfigurationManager.AppSettings["UnitName"];
      //    permission.UnitCode = ConfigurationManager.AppSettings["UnitCode"];
      //    permission.Username = ConfigurationManager.AppSettings["Username"];
      //    permission._role = ConfigurationManager.AppSettings["Role"];
      //    permission._roleOpc =
      //      (Opc)Enum.Parse(typeof(Opc), ConfigurationManager.AppSettings["OpcPermission"]);
      //  }
      //}
      //catch (Exception ex)
      //{
      //  throw new Exception("Something is wrong in configuration file.", ex);
      //}
      #endregion
      
      return permission;
    }

    public static Opc GetOpcPermission(string maDonVi, bool quyenDieuTri01, bool quyenDieuTri02)
    {
      Opc permissionCode = Opc.None;

      if (maDonVi == "01")
        permissionCode = Opc.Permission03;
      else if (quyenDieuTri01 && quyenDieuTri02)
        permissionCode = Opc.Permission03;
      else if (quyenDieuTri01)
        permissionCode = Opc.Permission01;
      else if (quyenDieuTri02)
        permissionCode = Opc.Permission02;

      return permissionCode;
    }

    #endregion

    #region Public Methods

    /// <summary>
    /// Check function of this permission.
    /// </summary>
    /// <param name="function">Function that need to check permission.</param>
    /// <returns></returns>
    public bool HasPermission(Functions function)
    {
      string role = _role;
      bool hasPermission = false;

      switch (function)
      {
        #region CapNhatThongTinDieuTri
        case Functions.CapNhatThongTinDieuTri:
          hasPermission = Array.Exists<string>(CapNhatThongTinDieuTri, p => role.IndexOf(p) > -1);
          break;
        #endregion
        #region ChamDutDieuTri
        case Functions.ChamDutDieuTri:
          hasPermission = Array.Exists<string>(ChamDutDieuTri, p => role.IndexOf(p) > -1);
          hasPermission &=
            (_roleOpc == Opc.Permission02 || _roleOpc == Opc.Permission03);
          break;
        #endregion
        #region DangKyCapThe
        case Functions.DangKyCapThe:
          hasPermission = Array.Exists<string>(DangKyCapThe, p => role.IndexOf(p) > -1);
          break;
        #endregion
        #region DangKyDieuTriIO
        case Functions.DangKyDieuTriIO:
          hasPermission = Array.Exists<string>(DangKyDieuTriIO, p => role.IndexOf(p) > -1);
          hasPermission &=
            (_roleOpc == Opc.Permission01 || _roleOpc == Opc.Permission03);
          break;
        #endregion
        #region DangKyDieuTriArv
        case Functions.DangKyDieuTriArv:
          hasPermission = Array.Exists<string>(DangKyDieuTriArv, p => role.IndexOf(p) > -1);
          hasPermission &=
            (_roleOpc == Opc.Permission02 || _roleOpc == Opc.Permission03);
          break;
        #endregion
        #region DoiMatKhau
        case Functions.DoiMatKhau:
          hasPermission = Array.Exists<string>(DoiMatKhau, p => role.IndexOf(p) > -1);
          break;
        #endregion
        #region DuyetDangKy
        case Functions.DuyetDangKy:
          hasPermission = Array.Exists<string>(DuyetDangKy, p => role.IndexOf(p) > -1);
          break;
        #endregion
        #region ThayDoiThongTinCapThe
        case Functions.ThayDoiThongTinCapThe:
          hasPermission = Array.Exists<string>(ThayDoiThongTinCapThe, p => role.IndexOf(p) > -1);
          break;
        #endregion
        #region ThayOpc
        case Functions.ThayOpc:
          hasPermission = Array.Exists<string>(ThayOpc, p => role.IndexOf(p) > -1);
          hasPermission &=
            (_roleOpc == Opc.Permission02 || _roleOpc == Opc.Permission03);
          break;
        #endregion
        #region ThayPhacDo
        case Functions.ThayPhacDo:
          hasPermission = Array.Exists<string>(ThayPhacDo, p => role.IndexOf(p) > -1);
          break;
        #endregion
        #region TimKiem
        case Functions.TimKiem:
          hasPermission = Array.Exists<string>(TimKiem, p => role.IndexOf(p) > -1);
          break;
        #endregion
        #region XemBaoCaoThang
        case Functions.XemBaoCaoThang:
          hasPermission = Array.Exists<string>(XemBaoCaoThang, p => role.IndexOf(p) > -1);
          break;
        #endregion
        #region XemDanhDachChuaDuyet
        case Functions.XemDanhDachChuaDuyet:
          hasPermission = Array.Exists<string>(XemDanhDachChuaDuyet, p => role.IndexOf(p) > -1);
          break;
        #endregion
        #region XemDanhDachDaDuyet
        case Functions.XemDanhDachDaDuyet:
          hasPermission = Array.Exists<string>(XemDanhDachDaDuyet, p => role.IndexOf(p) > -1);
          break;
        #endregion
        #region XemDanhSach
        case Functions.XemDanhSach:
          hasPermission = Array.Exists<string>(XemDanhSach, p => role.IndexOf(p) > -1);
          break;
        #endregion
        #region XemDanhSachArv
        case Functions.XemDanhSachArv:
          hasPermission = Array.Exists<string>(XemDanhSachArv, p => role.IndexOf(p) > -1);
          hasPermission &=
            (_roleOpc == Opc.Permission02 || _roleOpc == Opc.Permission03);
          break;
        #endregion
        #region XemDanhSachCapThe
        case Functions.XemDanhSachCapThe:
          hasPermission = Array.Exists<string>(XemDanhSachCapThe, p => role.IndexOf(p) > -1);
          break;
        #endregion
        #region XemDanhSachDieuTri
        case Functions.XemDanhSachDieuTri:
          hasPermission = Array.Exists<string>(XemDanhSachDieuTri, p => role.IndexOf(p) > -1);
          hasPermission &=
            (_roleOpc == Opc.Permission02 || _roleOpc == Opc.Permission03);
          break;
        #endregion
        #region XemDanhSachPhacDo
        case Functions.XemDanhSachPhacDo:
          hasPermission = Array.Exists<string>(XemDanhSachPhacDo, p => role.IndexOf(p) > -1);
          break;
        #endregion
        #region XemThongTinBenhNhan
        case Functions.XemLichSuChuyenBenh:
          hasPermission = Array.Exists<string>(XemLichSuChuyenBenh, p => role.IndexOf(p) > -1);
          break;
        #endregion

        #region default
        default:
          hasPermission = false;
          break;
        #endregion 
      }

      return hasPermission;
    }

    public bool ChangePassword(string oldPassword, string newPassword)
    {
      bool isValid;

      try
      {
        if (VerifyPassword(Username, oldPassword, _user))
        {
          string salt = CreateSalt(6);
          string passwordHash = CreatePasswordHash(newPassword, salt);

          //Change password here
          _user.Salt = salt;
          _user.Password = passwordHash;
          GlobalCache.UsersService.Save(_user);

          isValid = true;
        }
        else
        {
          isValid = false;
        }
      }
      catch (Exception ex)
      {
        Logger.LogCritical("Permission::ChangePassword", ex, "General", null);
        throw;
      }

      return isValid;
    }

    #endregion

    #region Helper Methods

    private static bool VerifyPassword(string username, string password, SUsers verifyingUser)
    {
      bool isValid;

      try
      {
        if (verifyingUser != null)
        {
          string dbHasedPassword = verifyingUser.Password;
          string dbSalt = verifyingUser.Salt;

          string hashedPasswordAndSalt = CreatePasswordHash(password, dbSalt);

          if (hashedPasswordAndSalt.Equals(dbHasedPassword))
            isValid = true;
          else
            isValid = false;
        }
        else
          isValid = false;
      }
      catch (Exception ex)
      {
        Logger.LogCritical("Permission::VerifyPassword", ex, "General", null);
        throw;
      }

      return isValid;
    }

    #region Hash Password

    private static string CreateSalt(int size)
    {
      // Generate a cryptographic random number using the cryptographic
      // service provider
      RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
      byte[] buff = new byte[size];
      rng.GetBytes(buff);
      // Return a Base64 string representation of the random number
      return Convert.ToBase64String(buff);
    }

    private static string CreatePasswordHash(string pwd, string salt)
    {
      return HashPassword(String.Concat(pwd, salt));
    }

    private static string HashPassword(string pwd)
    {
      SHA256 sha = new SHA256Managed();

      byte[] pwdBuff = System.Text.Encoding.Default.GetBytes(pwd);
      byte[] hashedPwd = sha.TransformFinalBlock(pwdBuff, 0, pwdBuff.Length);

      string hash = "";
      Array.ForEach(sha.Hash, b => hash += Convert.ToString(b, 16));
      sha.Clear();

      //hash = pwd;

      return hash;
    }

    #endregion

    #endregion
  }
}
